﻿//==============================================================================
// File: BootstrapperBase.cs
// Created: 2010-04-30
// Author: Piotr Włodek
//==============================================================================
// This file is a part of MAMMOTH project.
// Copyright (C) 2010 AGH University of Science and Technology, Krakow.
// https://caribou.iisg.agh.edu.pl/trac/mammoth
//
// Mammoth is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// Mammoth is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with Mammoth. If not, see http://www.gnu.org/licenses/.
//==============================================================================

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition.Hosting;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Caliburn.Core;
using Caliburn.PresentationFramework;
using Caliburn.PresentationFramework.ApplicationModel;
using WpfRcp.Controllers;
using WpfRcp.Core.Common;
using WpfRcp.Core.Controllers;
using WpfRcp.Core.Globals;
using WpfRcp.Core.Services.Commands;
using WpfRcp.Core.Startup;
using WpfRcp.Core.Threading;
using WpfRcp.Core.Views;
using WpfRcp.Core.Views.Presenters;
using WpfRcp.Helpers.Caliburn;
using WpfRcp.Helpers.CompositePresentation;
using WpfRcp.Services.Commands;
using WpfRcp.Views;
using WpfRcp.Views.Controls;
using WpfRcp.Views.Presenters;
using WpfRcp.Views.Windows;
using MefContrib.Integration.Unity;
using Microsoft.Practices.Composite.Events;
using Microsoft.Practices.Composite.Logging;
using Microsoft.Practices.Composite.Modularity;
using Microsoft.Practices.Composite.Regions;
using Microsoft.Practices.Composite.Presentation.Regions;
using Microsoft.Practices.Unity;
using WpfRcp.Core.Services;
using WpfRcp.Services;
using MenuItem = System.Windows.Controls.MenuItem;
using WpfRcp.Controls;

namespace WpfRcp.Startup
{
    public abstract class BootstrapperBase : CaliburnBootstrapper
    {
        private readonly SplashScreen m_FastSplashScreen;
        private SplashViewPresentationModel m_SplashScreenPresenter;
        private IUnityContainer m_Container;

        public bool IsRunning { get; private set; }

        public IList<string> Args { get; private set; }

        protected BootstrapperBase(IApplication application)
            : base(application)
        {
            // ReSharper disable DoNotCallOverridableMethodsInConstructor
            m_FastSplashScreen = GetSplashScreen();
            // ReSharper restore DoNotCallOverridableMethodsInConstructor
        }

        #region Protected Registering Methods
        
        protected override void RegisterServices()
        {
            Container.Register<IContainerFacade, UnityContainerFacade>(LifetimePolicy.Singleton);
            Container.Register<ISettingsService, SettingsService>(LifetimePolicy.Singleton);
            Container.Register<IExceptionManager, ExceptionManager>(LifetimePolicy.Singleton);
            Container.Register<IResourceManager, ResourceManagerImpl>(LifetimePolicy.Singleton);
            Container.Register<ICommandLineParser, CommandLineParser>();
            //Container.Register<ILoggerService, LoggerService>(LifetimePolicy.Singleton);
            Container.Register<IWorkbenchService, WorkbenchService>(LifetimePolicy.Singleton);
            Container.Register<ILanguageService, LanguageService>(LifetimePolicy.Singleton);
            Container.Register<IThemeService, ThemeService>(LifetimePolicy.Singleton);
            Container.Register<IActionService, ActionService>(LifetimePolicy.Singleton);
            Container.Register<IActionRegistry, ActionRegistry>(LifetimePolicy.Singleton);
            Container.Register<IMenuRegistry, MenuRegistry>(LifetimePolicy.Singleton);

            Container.Register<IMenuItemFactory, MenuItemBasedMenuItemFactory>(LifetimePolicy.Singleton);
            Container.Register<IStatusManager, StatusManager>(LifetimePolicy.Singleton);
            Container.Register<IWindowManagerExt, WindowManagerExt>(LifetimePolicy.Singleton);
            Container.Register<IContextMenuItemProvider, ContextMenuItemProvider>(LifetimePolicy.Singleton);
            Container.Register<IContextMenuItemFactory, MenuItemBasedContextMenuItemFactory>(LifetimePolicy.Singleton);
            
            // Register global RcpSettings
            Container.Register<IRcpSettings, RcpSettings>(LifetimePolicy.Singleton);

            // Register dock manager
            Container.Register<RegionBasedTabMenuDockManager, RegionBasedTabMenuDockManager>(LifetimePolicy.Singleton);

            // Register dialog controller
            Container.Register<IDialogController, DialogController>(LifetimePolicy.Singleton);
        }

        protected override void InitializeServices()
        {
            var settingsService = Container.Resolve<ISettingsService>();
            var languageService = Container.Resolve<ILanguageService>();
            var themeService = Container.Resolve<IThemeService>();
            var rcpSettings = Container.Resolve<IRcpSettings>();

            // Set current culture from settings
            languageService.CurrentCulture = settingsService.Settings.CurrentCulture;
            themeService.CurrentTheme = (Theme)settingsService.Settings.CurrentTheme;

            // Show that the app is ready
            rcpSettings.StatusMessageLeft = Properties.Resources.Ready;

            // Apply proper async execution method
            if (settingsService.Settings.UseThreadPool)
            {
                JobConfigurator.SetExecutor(new Job.ThreadPoolBasedAsyncExecutor(settingsService.Settings.ThreadPoolSize));
            }
            else
            {
                JobConfigurator.SetExecutor(new Job.DelegateBasedAsyncExecutor());
            }

            SetRcpSettings(rcpSettings);
        }

        protected override void RegisterPresenters()
        {
            Container.Register<LogViewPresentationModel, LogViewPresentationModel>(LifetimePolicy.Singleton);
            Container.Register<IShellPresentationModel, ShellPresentationModel>(LifetimePolicy.Singleton);
            Container.Register<ModulesInfoPresentationModel, ModulesInfoPresentationModel>(LifetimePolicy.Singleton);
            Container.Register<AboutViewPresentationModel, AboutViewPresentationModel>(LifetimePolicy.Singleton);
            Container.Register<ApplicationOptionsViewPresentationModel, ApplicationOptionsViewPresentationModel>(LifetimePolicy.Singleton);
            Container.Register<CommonOptionsViewPresentationModel, CommonOptionsViewPresentationModel>(LifetimePolicy.Singleton);
            Container.Register<ThreadingOptionsViewPresentationModel, ThreadingOptionsViewPresentationModel>(LifetimePolicy.Singleton);
            Container.Register<StatusViewPresentationModel, StatusViewPresentationModel>(LifetimePolicy.Singleton);
        }

        protected override void RegisterViews()
        {
            Container.Register<IShell, Shell>(LifetimePolicy.Singleton);
            Container.Register<IMenuControl, MenuControl>(LifetimePolicy.Singleton);
            Container.Register<IApplicationOptionsView, ApplicationOptionsWindow>(LifetimePolicy.Singleton);
            Container.Register<ILogView, LogWindow>(LifetimePolicy.Singleton);
            Container.Register<ISplashView, SplashScreenWindow>(LifetimePolicy.Singleton);
            Container.Register<IAboutView, AboutWindow>(LifetimePolicy.Singleton);
            Container.Register<IModulesInfoView, ModulesInfoWindow>(LifetimePolicy.Singleton);
            Container.Register<ICommonOptionsView, CommonOptions>(LifetimePolicy.Singleton);
            Container.Register<IThreadingOptionsView, ThreadingOptions>(LifetimePolicy.Singleton);

            Container.Register<IJobsView, JobsView>(LifetimePolicy.Singleton);

            Container.Register<IMessageBox, MessageBoxViewPresentationModel>();
            Container.Register<IStatusView, StatusWindow>();
            Container.Register<IMessageBoxView, MessageBoxWindow>();
            Container.Register<IProgressView, ProgressControl>();
            Container.Register<IWindowedProgressView, ProgressWindow>();

            var regionManager = Container.Resolve<IRegionManager>();
            regionManager.RegisterViewWithRegion(RegionNames.OptionsRegion, typeof(ICommonOptionsView));
            regionManager.RegisterViewWithRegion(RegionNames.OptionsRegion, typeof(IThreadingOptionsView));

            regionManager.RegisterViewWithRegion(RegionNames.LogRegion, () => Container.Resolve<LoggerViewPresentationModel>().View);
            regionManager.RegisterViewWithRegion(RegionNames.LogRegion, () => Container.Resolve<JobsViewPresentationModel>().View);
        }

        #endregion

        #region Virtual / Abstract Members

        protected virtual void OnExit()
        {
            if (IsRunning)
            {
                // Save the application settings
                Container.Resolve<ISettingsService>().Save();

                Container.Resolve<IEventAggregator>().GetEvent<ApplicationStoppedEvent>().Publish(null);

                Container.Resolve<ILoggerService>().Log(Properties.Resources.AppStoppedSuccessfuly, "WpfRcp.UI",
                                                        TraceEventType.Stop, Properties.Resources.Application);
            }
        }

        protected virtual void OnUnhandledException(Exception exception)
        {
            // Handle global error
            HandleException(exception);

            try
            {
                var messageBox = Container.Resolve<IMessageBox>();
                messageBox.Show(Properties.Resources.CriticalErrorTitle,
                                Properties.Resources.CriticalErrorMessage,
                                MessageBoxViewButtons.OK, MessageBoxViewIcons.Error);
            }
            catch
            {
                // Nope here
            }
        }
        
        protected virtual void OnStartupUnhandledException(Exception exception)
        {
            // Handle global error
           HandleException(exception);

            // Because error has occured during application startup, close it
            Application.Shutdown(1);
        }

        private void HandleException(Exception exception)
        {
            var manager = Container != null ? Container.Resolve<IExceptionManager>() : new ExceptionManager();
            //var logger = Container != null ? Container.Resolve<ILoggerService>() : new LoggerService();
            var logger = Container.Resolve<ILoggerService>();
            
            var typeLoadException = exception as ReflectionTypeLoadException;
            if (typeLoadException != null)
            {
                HandleExceptionCore(exception, manager);

                foreach (var loaderException in typeLoadException.LoaderExceptions)
                {
                    HandleExceptionCore(loaderException, manager);
                }

                if (typeLoadException.Types != null && typeLoadException.Types.Length > 0)
                {
                    var builder = new StringBuilder();
                    foreach (var type in typeLoadException.Types)
                    {
                        if (type == null) continue;
                        builder.AppendLine(type.FullName);
                    }

                    logger.Log("Loaded types:\n" + builder, Category.Debug, Priority.High);
                }
            }
            else
            {
                HandleExceptionCore(exception, manager);
            }
        }

        private void HandleExceptionCore(Exception exception, IExceptionManager exceptionManager)
        {
            if (exception != null)
            {
                exceptionManager.HandleException(exception, "UI Global Policy");   
            }
        }

        protected virtual void ConfigureRegionAdapterMappings(RegionAdapterMappings regionAdapterMappings)
        {
            regionAdapterMappings.RegisterMapping(typeof(Menu),
                Container.Resolve<GroupingItemsControlRegionAdapter>());

            regionAdapterMappings.RegisterMapping(typeof(MenuItem),
                Container.Resolve<GroupingItemsControlRegionAdapter>());

            regionAdapterMappings.RegisterMapping(typeof(ContextMenu),
                Container.Resolve<GroupingItemsControlRegionAdapter>());
        }

        protected virtual SplashScreen GetSplashScreen()
        {
            return null;
        }

        protected abstract void SetRcpSettings(IRcpSettings settings);

        protected abstract IModuleCatalog GetModuleCatalog();

        #endregion

        #region Protected Create Overides

        protected override IContainerFacade CreateContainer()
        {
            // Create Unity
            m_Container = new UnityContainer();

            // Add MEF catalogs to the Unity
            m_Container.RegisterCatalog(new AssemblyCatalog(Assembly.GetEntryAssembly()));
            m_Container.RegisterCatalog(new DirectoryCatalog("."));

            return new UnityContainerFacade(m_Container);
        }

        protected override IContainer CreateCaliburnContainer()
        {
            return new ExtendedUnityAdapter(m_Container);
        }

        protected override void ConfigureCaliburn(CoreConfiguration configuration)
        {
            configuration
                .WithPresentationFramework()
                .UsingWindowManager<RcpWindowManager>()
                .WithExtendedCompositeApplicationLibrary(CreateShell)
                .WithImmediateComponentLoading()
                .AfterConfigureRegionAdapterMappings(ConfigureRegionAdapterMappings)
                .WithModuleCatalog(GetModuleCatalog());
        }

        private DependencyObject CreateShell()
        {
            // Get the settings
            var settings = Container.Resolve<IApplicationSettings>();

            if (!settings.NoSplash)
            {
                // Show super extreme underground supreme bombastic robust WPF splash instead of the png image
                m_SplashScreenPresenter = Container.Resolve<SplashViewPresentationModel>();
                m_SplashScreenPresenter.View.Show();

                // Close the fast splash
                if (m_FastSplashScreen != null)
                    m_FastSplashScreen.Close(TimeSpan.FromSeconds(0.0));
            }

            // Prepare main form
            var shellPresenter = Container.Resolve<IShellPresentationModel>();
            var windowManager = Container.Resolve<IWindowManager>();
            var viewStrategy = Container.Resolve<IViewStrategy>();
            var shell = (DependencyObject)viewStrategy.GetView(shellPresenter, null, null);

            Application.RootVisual = shell;
            Application.ShutdownMode = ShutdownMode.OnMainWindowClose;

            // Show main form
            windowManager.Show(shellPresenter, null, ExecuteShutdownModel);

            if (!settings.NoSplash)
            {
                // Let the splash screen overlay the shell
                m_SplashScreenPresenter.View.ViewOwner = shell;
                m_SplashScreenPresenter.View.Focus();
            }

            //return shellPresenter.View as DependencyObject;
            return shell;
        }

        private void ExecuteShutdownModel(ISubordinate subordinate, Action completed)
        {
            completed();
        }

        #endregion

        #region Before / After Start Methods

        protected override void BeforeStart(CoreConfiguration core)
        {
            base.BeforeStart(core);

            var commandLine = Container.Resolve<ICommandLineParser>();
            commandLine.Parse(Args);
        }

        protected override void AfterStart()
        {
            // Get the settings
            var settings = Container.Resolve<IApplicationSettings>();

            // Get logger
            var loggerService = Container.Resolve<ILoggerService>();
            loggerService.Log("Creating shell.", Category.Debug, Priority.Low);

            if (!settings.NoSplash)
            {
                if (m_SplashScreenPresenter != null)
                    m_SplashScreenPresenter.View.Close();
            }
            else
            {
                // Close the fast splash
                if (m_FastSplashScreen != null)
                    m_FastSplashScreen.Close(TimeSpan.FromSeconds(0.0));
            }

            // Initialize all actions
            var menuRegistry = Container.Resolve<IMenuRegistry>();
            menuRegistry.Run();

            // Log that application has started normally
            loggerService.Log(Properties.Resources.AppStartedSuccessfuly, "WpfRcp.UI",
                              TraceEventType.Start, Properties.Resources.Application);

            // Log the CLR version
            loggerService.Log(string.Format(Properties.Resources.RunningOnClrVersion, Environment.Version),
                              "WpfRcp.UI", TraceEventType.Information, Properties.Resources.Application);

            // Detect rendering tier
            var tier = RenderCapability.Tier >> 16;
            loggerService.Log(string.Format(Properties.Resources.TierDetected, tier),
                              "WpfRcp.UI", TraceEventType.Information, Properties.Resources.Application);

            // Log proper async execution method
            if (settings.UseThreadPool)
            {
                loggerService.Log(string.Format(Properties.Resources.UsingThreadPoolBasedAsyncExec, settings.ThreadPoolSize),
                                  "WpfRcp.UI", TraceEventType.Information, Properties.Resources.Application);
            }
            else
            {
                loggerService.Log(string.Format(Properties.Resources.UsingDelegateBasedAsyncExec),
                                  "WpfRcp.UI", TraceEventType.Information, Properties.Resources.Application);
            }

            // Log that application has started normally
            loggerService.Log(Properties.Resources.ShellStartedSuccesfully, "WpfRcp.UI",
                              TraceEventType.Start, Properties.Resources.Application);

            // Raise ApplicationStartedEvent event
            Container.Resolve<IEventAggregator>().GetEvent<ApplicationStartedEvent>().Publish(null);
        }

        #endregion

        #region Internal Methods

        internal void UnhandledException(Exception exception)
        {
            OnUnhandledException(exception);
        }

        internal void StartupUnhandledException(Exception exception)
        {
            OnStartupUnhandledException(exception);
        }

        /// <summary>
        /// Starts the bootstrapper. This is the preffered way to start the application.
        /// </summary>
        internal void Start(string[] args)
        {
            IsRunning = true;
            Args = new List<string>(args);

            // Show initial splash screen very fast
            if (m_FastSplashScreen != null)
                m_FastSplashScreen.Show(false);

            // Name the UI thread
            Thread.CurrentThread.Name = ApplicationConstants.UIThreadName;

            // Run the base bootstrapper
            Run();
        }

        internal void Exit()
        {
            OnExit();
        }

        #endregion
    }
}